#include "UnixDomainSocket.h"
#include <cstdio>  // snprintf
#include <memory>
#include <netinet/tcp.h>
#include <strings.h>  // memZero
#include "InnerType.h"
#include "PlatLogger.h"
#include "SocketOpt.h"
#include "UnixDomainAddress.h"
#include "Util.h"

namespace xncc {
namespace foundation {
    using namespace shared;
    UnixDomainSocket::~UnixDomainSocket() = default;

    void UnixDomainSocket::bind()
    {
        if (FileBusi::exists(sockaddr_->toStr())) {
            FileBusi::remove(sockaddr_->toStr());
        }
        int ret = -1;
        if (sockaddr_->Type() == XNCC_ADDR_TYPE::UNIX_DOMAIN) {
            const UnixDomainAddress::s_ptr domainAddr = std::dynamic_pointer_cast<UnixDomainAddress>(sockaddr_);
            ret = ::bind(sockfd_, domainAddr->getSockAddr(), static_cast<socklen_t>(sizeof(struct sockaddr_un)));
        }
        if (ret < 0) {
            throw system_exception("bind socket failed");
        }
    }

    void UnixDomainSocket::listen()
    {
        int ret = ::listen(sockfd_, SOMAXCONN);
        if (ret < 0) {
            throw system_exception("listen socket failed");
        }
    }

    int UnixDomainSocket::accept(std::shared_ptr<XnccAddress> /*peeraddr*/)
    {
        /*unix domain*/
        struct sockaddr_un domainAddr = {};
        memZero(domainAddr);

        auto             addrlen = static_cast<socklen_t>(sizeof domainAddr);
        struct sockaddr* addr    = SocketOpt::sockaddr_cast(&domainAddr);

        int connfd = ::accept(sockfd_, addr, &addrlen);
        SocketOpt::setNonBlockAndCloseOnExec(connfd);
        if (connfd < 0) {  // continue accept
            // int savedErrno = errno;
            TRACE_ERROR << ("Socket::accept");
            // switch (savedErrno) {
            //     case EAGAIN:
            //     case ECONNABORTED:
            //     case EINTR:
            //     case EPROTO:  // ???
            //     case EPERM:
            //     case EMFILE:  // per-process lmit of open file desctiptor ???
            //         // expected errors
            //         errno = savedErrno;
            //         break;
            //     case EBADF:
            //     case EFAULT:
            //     case EINVAL:
            //     case ENFILE:
            //     case ENOBUFS:
            //     case ENOMEM:
            //     case ENOTSOCK:
            //     case EOPNOTSUPP:
            //         // unexpected errors
            //         throw system_exception("accept socket unexpected errors");
            //         break;
            //     default: {
            //         throw system_exception("accept socket unknown errors");
            //     } break;
            // }
        }
        if (connfd > 0) {
            const UnixDomainAddress::s_ptr domainAdr = std::dynamic_pointer_cast<UnixDomainAddress>(sockaddr_);
            domainAdr->setSockAddr(domainAddr);
        }
        return connfd;
    }

    void UnixDomainSocket::shutdownWrite() { SocketOpt::shutdownWrite(sockfd_); }

}  // namespace foundation
}  // namespace xncc